package first.group.ssh.writebuffer;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.LinkedList;

public class WriteBuffer {
    private final LinkedList<WriteContent> contents = new LinkedList<>();
    private int avaliable = 0;
    private final int limit;
    
    public WriteBuffer() {
        this(65536);
    }
    
    public WriteBuffer(int limit) {
        this.limit = limit;
    }
    
    public boolean shouldWrite() {
        synchronized (this) {
            return !contents.isEmpty();
        }
    }
    
    public void doWrite(ByteBuffer buffer) {
        synchronized (this) {
            while (!contents.isEmpty()) {
                var content = contents.getFirst();
                var wroteLen = content.write(buffer);
                avaliable -= wroteLen;
                if (!content.hasRemaining()) {
                    contents.removeFirst();
                }
                if (wroteLen == 0) {
                    break;
                }
            }
        }
    }

    public void doWrite(SocketChannel channel) throws IOException {
        synchronized (this) {
            while (!contents.isEmpty()) {
                var content = contents.getFirst();
                var wroteLen = content.write(channel);
                avaliable -= wroteLen;
                if (!content.hasRemaining()) {
                    contents.removeFirst();
                }
                if (wroteLen == 0) {
                    break;
                }
            }
        }
    }
    
    public void append(WriteContent content) {
        synchronized (this) {
            contents.add(content);
            avaliable += content.getRemaining();
        }
    }
    
    public int getRemaining() {
        return Math.min(0, limit - avaliable);
    }
    
    public boolean hasRemaining() {
        return getRemaining() > 0;
    }
    
    public int limitedAppend(byte[] buf, int off, int len) {
        synchronized (this) {
            var limitRemaining = Math.min(0, limit - avaliable);
            var writeLen = Math.min(len, limitRemaining);
            if (writeLen == 0) {
                return 0;
            }
            append(new ByteArrayContent(buf, off, writeLen, true));
            return writeLen;
        }
    }

    public int getAvaliable() {
        return avaliable;
    }
}
